/*
 * Copyright (C) 2006 The Android Open Source Project
 * Copyright (C) 2013 Daniel Himmelein
 * Copyright (C) 2016 E.S.R.Labs
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#ifndef MINDROID_CONTENT_SHAREDPREFERENCES_H_
#define MINDROID_CONTENT_SHAREDPREFERENCES_H_

#include <mindroid/lang/Object.h>
#include <mindroid/os/Handler.h>
#include <mindroid/util/HashMap.h>
#include <mindroid/util/HashSet.h>

namespace mindroid {

class String;

/**
 * Interface for accessing and modifying preference data returned by
 * {@link Context#getSharedPreferences}. For any particular set of preferences, there is a single
 * instance of this class that all clients share. Modifications to the preferences must go through
 * an {@link Editor} object to ensure the preference values remain in a consistent state and control
 * when they are committed to storage. Objects that are returned from the various <code>get</code>
 * methods must be treated as immutable by the application.
 *
 * <p>
 * <em>Note: currently this class does not support use across multiple
 * processes.  This will be added later.</em>
 *
 * <div class="special reference">
 * <h3>Developer Guides</h3>
 * <p>
 * For more information about using SharedPreferences, read the <a href="{@docRoot}
 * guide/topics/data/data-storage.html#pref">Data Storage</a> developer guide.
 * </p>
 * </div>
 *
 * @see Context#getSharedPreferences
 */
class SharedPreferences :
        public Object {
public:
    virtual ~SharedPreferences() = default;

    /**
     * Interface definition for a callback to be invoked when a shared preference is changed.
     */
    class OnSharedPreferenceChangeListener :
            public Object {
    public:
        /**
         * Called when a shared preference is changed, added, or removed. This may be called even if
         * a preference is set to its existing value.
         *
         * <p>
         * This callback will be run on the specified looper thread.
         *
         * @param sharedPreferences The {@link SharedPreferences} that received the change.
         * @param key The key of the preference that was changed, added, or removed.
         */
        inline void onSharedPreferenceChanged(const sp<SharedPreferences>& sharedPreferences, const char* key) {
            onSharedPreferenceChanged(sharedPreferences, String::valueOf(key));
        }
        virtual void onSharedPreferenceChanged(const sp<SharedPreferences>& sharedPreferences, const sp<String>& key) = 0;

        /**
         * Called when a shared preference is changed, added, or removed. This may be called even if
         * a preference is set to its existing value.
         *
         * <p>
         * This callback will be run on the specified looper thread.
         *
         * @param sharedPreferences The {@link SharedPreferences} that received the change.
         */
        virtual void onSharedPreferenceChanged(const sp<SharedPreferences>& sharedPreferences) {
        }
    };

    /**
     * Interface used for modifying values in a {@link SharedPreferences} object. All changes you
     * make in an editor are batched, and not copied back to the original {@link SharedPreferences}
     * until you call {@link #commit} or {@link #apply}
     */
    class Editor :
            public Object {
    public:
        /**
         * Set a String value in the preferences editor, to be written back once {@link #commit} or
         * {@link #apply} are called.
         *
         * @param key The name of the preference to modify.
         * @param value The new value for the preference.
         *
         * @return Returns a reference to the same Editor object, so you can chain put calls
         * together.
         */
        inline sp<Editor> putString(const char* key, const char* value) {
            return putString(String::valueOf(key), String::valueOf(value));
        }
        virtual sp<Editor> putString(const sp<String>& key, const sp<String>& value) = 0;

        /**
         * Set a set of String values in the preferences editor, to be written back once
         * {@link #commit} is called.
         *
         * @param key The name of the preference to modify.
         * @param values The new values for the preference.
         * @return Returns a reference to the same Editor object, so you can chain put calls
         * together.
         */
        inline sp<Editor> putStringSet(const char* key, const sp<HashSet<sp<String>>>& values) {
            return putStringSet(String::valueOf(key), values);
        }
        virtual sp<Editor> putStringSet(const sp<String>& key, const sp<HashSet<sp<String>>>& values) = 0;

        /**
         * Set an int value in the preferences editor, to be written back once {@link #commit} or
         * {@link #apply} are called.
         *
         * @param key The name of the preference to modify.
         * @param value The new value for the preference.
         *
         * @return Returns a reference to the same Editor object, so you can chain put calls
         * together.
         */
        inline sp<Editor> putInt(const char* key, int32_t value) {
            return putInt(String::valueOf(key), value);
        }
        virtual sp<Editor> putInt(const sp<String>& key, int32_t value) = 0;

        /**
         * Set a long value in the preferences editor, to be written back once
         * {@link #commit} or {@link #apply} are called.
         *
         * @param key The name of the preference to modify.
         * @param value The new value for the preference.
         *
         * @return Returns a reference to the same Editor object, so you can
         * chain put calls together.
         */
        inline sp<Editor> putLong(const char* key, int64_t value) {
            return putLong(String::valueOf(key), value);
        }
        virtual sp<Editor> putLong(const sp<String>& key, int64_t value) = 0;

        /**
         * Set a float value in the preferences editor, to be written back once {@link #commit} or
         * {@link #apply} are called.
         *
         * @param key The name of the preference to modify.
         * @param value The new value for the preference.
         *
         * @return Returns a reference to the same Editor object, so you can chain put calls
         * together.
         */
        inline sp<Editor> putFloat(const char* key, float value) {
            return putFloat(String::valueOf(key), value);
        }
        virtual sp<Editor> putFloat(const sp<String>& key, float value) = 0;

        /**
         * Set a boolean value in the preferences editor, to be written back once {@link #commit} or
         * {@link #apply} are called.
         *
         * @param key The name of the preference to modify.
         * @param value The new value for the preference.
         *
         * @return Returns a reference to the same Editor object, so you can chain put calls
         * together.
         */
        inline sp<Editor> putBoolean(const char* key, bool value) {
            return putBoolean(String::valueOf(key), value);
        }
        virtual sp<Editor> putBoolean(const sp<String>& key, bool value) = 0;

        /**
         * Mark in the editor that a preference value should be removed, which will be done in the
         * actual preferences once {@link #commit} is called.
         *
         * <p>
         * Note that when committing back to the preferences, all removals are done first,
         * regardless of whether you called remove before or after put methods on this editor.
         *
         * @param key The name of the preference to remove.
         *
         * @return Returns a reference to the same Editor object, so you can chain put calls
         * together.
         */
        inline sp<Editor> remove(const char* key) {
            return remove(String::valueOf(key));
        }
        virtual sp<Editor> remove(const sp<String>& key) = 0;

        /**
         * Mark in the editor to remove <em>all</em> values from the preferences. Once commit is
         * called, the only remaining preferences will be any that you have defined in this editor.
         *
         * <p>
         * Note that when committing back to the preferences, the clear is done first, regardless of
         * whether you called clear before or after put methods on this editor.
         *
         * @return Returns a reference to the same Editor object, so you can chain put calls
         * together.
         */
        virtual sp<Editor> clear() = 0;

        /**
         * Commit your preferences changes back from this Editor to the {@link SharedPreferences}
         * object it is editing. This atomically performs the requested modifications, replacing
         * whatever is currently in the SharedPreferences.
         *
         * <p>
         * Note that when two editors are modifying preferences at the same time, the last one to
         * call commit wins.
         *
         * <p>
         * If you don't care about the return value and you're using this from your application's
         * main thread, consider using {@link #apply} instead.
         *
         * @return Returns true if the new values were successfully written to persistent storage.
         */
        virtual bool commit() = 0;

        /**
         * Commit your preferences changes back from this Editor to the {@link SharedPreferences}
         * object it is editing. This atomically performs the requested modifications, replacing
         * whatever is currently in the SharedPreferences.
         *
         * <p>
         * Note that when two editors are modifying preferences at the same time, the last one to
         * call apply wins.
         *
         * <p>
         * Unlike {@link #commit}, which writes its preferences out to persistent storage
         * synchronously, {@link #apply} commits its changes to the in-memory
         * {@link SharedPreferences} immediately but starts an asynchronous commit to disk and you
         * won't be notified of any failures. If another editor on this {@link SharedPreferences}
         * does a regular {@link #commit} while a {@link #apply} is still outstanding, the
         * {@link #commit} will block until all async commits are completed as well as the commit
         * itself.
         *
         * <p>
         * As {@link SharedPreferences} instances are singletons within a process, it's safe to
         * replace any instance of {@link #commit} with {@link #apply} if you were already ignoring
         * the return value.
         *
         * <p>
         * You don't need to worry about Mindroid component lifecycles and their interaction with
         * <code>apply()</code> writing to disk. The framework makes sure in-flight disk writes from
         * <code>apply()</code> complete before switching states.
         *
         * <p class='note'>
         * The SharedPreferences.Editor interface isn't expected to be implemented directly.
         * However, if you previously did implement it and are now getting errors about missing
         * <code>apply()</code>, you can simply call {@link #commit} from <code>apply()</code>.
         */
        virtual void apply() = 0;
    };

    /**
     * Retrieve all values from the preferences.
     *
     * <p>
     * Note that you <em>must not</em> modify the collection returned by this method, or alter any
     * of its contents. The consistency of your stored data is not guaranteed if you do.
     *
     * @return Returns a map containing a list of pairs key/value representing the preferences.
     *
     * @throws NullPointerException
     */
    virtual sp<HashMap<sp<String>, sp<Variant>>> getAll() = 0;

    /**
     * Retrieve a String value from the preferences.
     *
     * @param key The name of the preference to retrieve.
     * @param defValue Value to return if this preference does not exist.
     *
     * @return Returns the preference value if it exists, or defValue. Throws ClassCastException if
     * there is a preference with this name that is not a String.
     *
     * @throws ClassCastException
     */
    inline sp<String> getString(const char* key, const char* defValue) {
        return getString(String::valueOf(key), String::valueOf(defValue));
    }
    inline sp<String> getString(const sp<String>& key, const char* defValue) {
        return getString(key, String::valueOf(defValue));
    }
    inline sp<String> getString(const char* key, const sp<String>& defValue) {
        return getString(String::valueOf(key), defValue);
    }
    virtual sp<String> getString(const sp<String>& key, const sp<String>& defValue) = 0;

    /**
     * Retrieve a set of String values from the preferences.
     *
     * <p>
     * Note that you <em>must not</em> modify the set instance returned by this call. The
     * consistency of the stored data is not guaranteed if you do, nor is your ability to modify the
     * instance at all.
     *
     * @param key The name of the preference to retrieve.
     * @param defValues Values to return if this preference does not exist.
     *
     * @return Returns the preference values if they exist, or defValues. Throws ClassCastException
     * if there is a preference with this name that is not a Set.
     *
     * @throws ClassCastException
     */
    inline sp<HashSet<sp<String>>> getStringSet(const char* key, const sp<HashSet<sp<String>>>& defValues) {
        return getStringSet(String::valueOf(key), defValues);
    }
    virtual sp<HashSet<sp<String>>> getStringSet(const sp<String>& key, const sp<HashSet<sp<String>>>& defValues) = 0;

    /**
     * Retrieve an int value from the preferences.
     *
     * @param key The name of the preference to retrieve.
     * @param defValue Value to return if this preference does not exist.
     *
     * @return Returns the preference value if it exists, or defValue. Throws ClassCastException if
     * there is a preference with this name that is not an int.
     *
     * @throws ClassCastException
     */
    inline int32_t getInt(const char* key, int32_t defValue) {
        return getInt(String::valueOf(key), defValue);
    }
    virtual int32_t getInt(const sp<String>& key, int32_t defValue) = 0;

    /**
     * Retrieve a long value from the preferences.
     *
     * @param key The name of the preference to retrieve.
     * @param defValue Value to return if this preference does not exist.
     *
     * @return Returns the preference value if it exists, or defValue. Throws ClassCastException if
     * there is a preference with this name that is not a long.
     *
     * @throws ClassCastException
     */
    inline int64_t getLong(const char* key, int64_t defValue) {
        return getLong(String::valueOf(key), defValue);
    }
    virtual int64_t getLong(const sp<String>& key, int64_t defValue) = 0;

    /**
     * Retrieve a float value from the preferences.
     *
     * @param key The name of the preference to retrieve.
     * @param defValue Value to return if this preference does not exist.
     *
     * @return Returns the preference value if it exists, or defValue. Throws ClassCastException if
     * there is a preference with this name that is not a float.
     *
     * @throws ClassCastException
     */
    inline float getFloat(const char* key, float defValue) {
        return getFloat(String::valueOf(key), defValue);
    }
    virtual float getFloat(const sp<String>& key, float defValue) = 0;

    /**
     * Retrieve a boolean value from the preferences.
     *
     * @param key The name of the preference to retrieve.
     * @param defValue Value to return if this preference does not exist.
     *
     * @return Returns the preference value if it exists, or defValue. Throws ClassCastException if
     * there is a preference with this name that is not a boolean.
     *
     * @throws ClassCastException
     */
    inline bool getBoolean(const char* key, bool defValue) {
        return getBoolean(String::valueOf(key), defValue);
    }
    virtual bool getBoolean(const sp<String>& key, bool defValue) = 0;

    /**
     * Checks whether the preferences contains a preference.
     *
     * @param key The name of the preference to check.
     * @return Returns true if the preference exists in the preferences, otherwise false.
     */
    inline bool contains(const char* key) {
        return contains(String::valueOf(key));
    }
    virtual bool contains(const sp<String>& key) = 0;

    /**
     * Create a new Editor for these preferences, through which you can make modifications to the
     * data in the preferences and atomically commit those changes back to the SharedPreferences
     * object.
     *
     * <p>
     * Note that you <em>must</em> call {@link Editor#commit} to have any changes you perform in the
     * Editor actually show up in the SharedPreferences.
     *
     * @return Returns a new instance of the {@link Editor} interface, allowing you to modify the
     * values in this SharedPreferences object.
     */
    virtual sp<Editor> edit() = 0;

    /**
     * Registers a callback to be invoked when a change happens to a preference.
     *
     * @param listener The callback that will run.
     * @see #unregisterOnSharedPreferenceChangeListener
     */
    virtual void registerOnSharedPreferenceChangeListener(const sp<OnSharedPreferenceChangeListener>& listener) = 0;

    /**
     * Unregisters a previous callback.
     *
     * @param listener The callback that should be unregistered.
     * @see #registerOnSharedPreferenceChangeListener
     */
    virtual void unregisterOnSharedPreferenceChangeListener(const sp<OnSharedPreferenceChangeListener>& listener) = 0;
};

} /* namespace mindroid */

#endif /* MINDROID_CONTENT_SHAREDPREFERENCES_H_ */
